// phalanx shared_array.hpp.cu header file

// Copyright (c) 2010 - 2011 Kohei Takahashi (Flast).
// Distributed under the MIT license. for more detail see COPYING.

#ifndef IG_PHALANX_SHARED_ARRAY_HPP_CU_ONCE_
#define IG_PHALANX_SHARED_ARRAY_HPP_CU_ONCE_

#include <phalanx/config.hpp.cu>

#include <boost/preprocessor/repeat.hpp>
#include <boost/preprocessor/inc.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/comma_if.hpp>
#include <boost/preprocessor/comparison/not_equal.hpp>

#include <phalanx/detail/retypedef.hpp.cu>

#include <phalanx/type_traits/detail/builtin_traits.hpp.cu>
#include <phalanx/type_traits/detail/is_builtin_vector.hpp.cu>

#include <phalanx/utility/detail/referenced_vector.hpp.cu>
#include <phalanx/utility/enable_if.hpp.cu>

#include <phalanx/array.hpp.cu>

namespace phalanx
{

namespace array_detail
{

/*
  FIXME: allocate array with packing when N is less than bank count

  ex) N = 7 and CC1.x with int3/float3
    bank |  1 ...  7 |  8 ... 14 |   15 ... 16   |
         | <-- x --> | <-- y --> | <- padding -> |
         | <-- z --> | <------- unused --------> |
 */
template < typename T,
  typename vector_type_traits< T >::size_type N,
  typename vector_type_traits< T >::size_type sizeof_value
    = sizeof( vector_type_traits< T >::value_type ) >
struct shared_array_impl
{
    typedef vector_type_traits< T > traits_type;
    PHALANX_PP_RETYPEDEF( traits_type, value_type );
    PHALANX_PP_RETYPEDEF( traits_type, pointer );
    PHALANX_PP_RETYPEDEF( traits_type, const_pointer );
    PHALANX_PP_RETYPEDEF( traits_type, reference );
    PHALANX_PP_RETYPEDEF( traits_type, const_reference );
    PHALANX_PP_RETYPEDEF( traits_type, size_type );
    PHALANX_PP_RETYPEDEF( traits_type, difference_type );

    static const size_type size_value = N;

private:
    template < size_type x, size_type align >
    struct alignup_
    {
        static const size_type value =
          ( ( x / align ) + ( x % align ? 1 : 0 ) ) * align;
    };

protected:
    static const size_type ratio_      = ( sizeof_value > 4 ) ? 1 : ( 4 / sizeof_value );
    static const size_type banks_      = PHALANX_CUDA_SHARED_MEM_BANKS;
    static const size_type bank_width_ = banks_ * ratio_;
    static const size_type max_size_   = alignup_< size_value, bank_width_ >::value;
    static const size_type inst_size_  = max_size_ * traits_type::size_value;

    typedef array< value_type, inst_size_ > impl_type;
    impl_type impl_;

    struct dimension_based_index
    {
        template < size_type sv_ >
        PHALANX_CONFIG_DEFAULT_FUNCTION( false )
        static typename enable_if_c< ( sv_ < 4 ), size_type >::type
        value_impl( size_type x_, size_type dim )
        {
            return dim * max_size_
                 + ( x_ / bank_width_ ) * bank_width_
                 + ( x_ * ratio_ ) % bank_width_
                 + ( x_ % bank_width_ ) / banks_;
        }

        template < size_type sv_ >
        PHALANX_CONFIG_DEFAULT_FUNCTION( false )
        static typename enable_if_c< sv_ == 4, size_type >::type
        value_impl( size_type x_, size_type dim )
        { return dim * max_size_ + x_; }

        PHALANX_CONFIG_DEFAULT_FUNCTION( false )
        static size_type
        value( size_type x_, size_type dim )
        { return value_impl< sizeof_value >( x_, dim ); }
    };

    template < typename U,
      bool      = is_builtin_vector< U >::value,
      size_type = traits_type::size_value >
    struct shared_array_at_;

    typedef shared_array_at_< T > at_type;

    template < typename U, bool, size_type >
    struct shared_array_at_
    {
        PHALANX_PP_RETYPEDEF( traits_type, reference );
        PHALANX_PP_RETYPEDEF( traits_type, const_reference );
        PHALANX_CONFIG_DEFAULT_FUNCTION( false )
        static reference
        at( impl_type &impl, size_type x_ )
        { return impl[ dimension_based_index::value( x_, 1 ) ]; }

        PHALANX_CONFIG_DEFAULT_FUNCTION( false )
        static const_reference
        at( const impl_type &impl, size_type x_ )
        { return impl[ dimension_based_index::value( x_, 1 ) ]; }
    };

#define EXPAND_VEC_PARAM_( unused_z_, num_, term_num_ ) \
  impl[ dimension_based_index::value( x_, num_ ) ] \
  BOOST_PP_COMMA_IF( BOOST_PP_NOT_EQUAL( num_, term_num_ ) )

#define AT_IMPL_( z_, num_, unused_param_ ) \
  template < typename U > \
  struct shared_array_at_< U, true, BOOST_PP_INC( num_ ) > \
  { \
      typedef ref_vector_impl_< U >       reference; \
      typedef ref_vector_impl_< const U > const_reference; \
  \
      PHALANX_CONFIG_DEFAULT_FUNCTION( false ) \
      static reference \
      at( impl_type &impl, size_type x_ ) \
      { \
          return reference( BOOST_PP_CAT( BOOST_PP_REPEAT_, z_ )( \
            BOOST_PP_INC( num_ ), EXPAND_VEC_PARAM_, num_ ) ); \
      } \
  \
      PHALANX_CONFIG_DEFAULT_FUNCTION( false ) \
      static const_reference \
      at( const impl_type &impl, size_type x_ ) \
      { \
          return const_reference( BOOST_PP_CAT( BOOST_PP_REPEAT_, z_ )( \
            BOOST_PP_INC( num_ ), EXPAND_VEC_PARAM_, num_ ) ); \
      } \
  };

    BOOST_PP_REPEAT( 4, AT_IMPL_, BOOST_PP_NIL );

#undef AT_IMPL_
#undef EXPAND_VEC_PARAM_

public:
    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    size_type
    size( void ) const
    { return size_value; }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    size_type
    max_size( void ) const
    { return max_size_; }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    bool
    empty( void ) const
    { return impl_.empty(); }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    typename impl_type::pointer
    data( void )
    { return impl_.data(); }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    typename impl_type::const_pointer
    data( void ) const
    { return impl_.data(); }
};

} // namespace array_detail

template < typename T,
  typename vector_type_traits< T >::size_type N >
struct shared_array
  : public array_detail::shared_array_impl< T, N >
{
private:
    typedef array_detail::shared_array_impl< T, N > impl_type;
    using impl_type::pointer;
    using impl_type::const_pointer;

    PHALANX_PP_RETYPEDEF( impl_type, at_type );

public:
    PHALANX_PP_RETYPEDEF( at_type, reference );
    PHALANX_PP_RETYPEDEF( at_type, const_reference );
    PHALANX_PP_RETYPEDEF( impl_type, size_type );

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    reference
    at( size_type x_ )
    { return at_type::at( impl_, x_ ); }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    const_reference
    at( size_type x_ ) const
    { return at_type::at( impl_, x_ ); }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    reference
    operator[]( size_type x_ )
    { return at( x_ ); }

    PHALANX_CONFIG_DEFAULT_FUNCTION( false )
    const_reference
    operator[]( size_type x_ ) const
    { return at( x_ ); }
};

} // namespace phalanx

#endif // IG_PHALANX_SHARED_ARRAY_HPP_CU_ONCE_

